package org.nlp2rdf.navigator.benchmark;

import com.google.common.base.Joiner;
import com.google.common.collect.TreeMultimap;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSetFormatter;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import org.aksw.commons.semweb.sparql.core.ISparqlEndpoint;
import org.aksw.commons.semweb.sparql.core.QueryCollection;
import org.aksw.commons.util.collections.MultiMaps;
import org.aksw.commons.util.collections.NaturalComparator;
import org.aksw.commons.util.collections.ResourceComparator;

import java.util.*;
import java.util.regex.Pattern;

/**
 * Created by Claus Stadler
 * Date: Oct 9, 2010
 * Time: 6:06:55 PM
 */
public class MySparqlTasks
{
    /**
     * Retrieves all types (classes) of the given set of instances.
     *
     * @param sparqlEndpoint
     * @param instances
     * @param includeAll whether instances without parent classes should be part of the result. FIXME Not implemented yet.
     * @return
     */
    /*
    String query = SparqlQueryBuilder.
        select().var("c").var("i").gp().
            s("i").p(RDF.type).o("c").
            filterIn("i", instances).toString()
    */
    static Map<Resource, Set<Resource>> getTypes(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, Collection<Resource> instances, boolean includeAll)
    {
        return getResourceMappingBySubject(sparqlEndpoint, graphNames, RDF.type, instances, includeAll);
    }

    static Map<Resource, Set<Resource>> getDirectSuperClasses(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, Collection<Resource> instances, boolean includeAll)
    {
        return getResourceMappingBySubject(sparqlEndpoint, graphNames, RDFS.subClassOf, instances, includeAll);
    }


    public static <T> NavigableSet<T> toNavigableSet(Collection<T> c, Comparator<? super T> comparator) {
        /*
        if(c instanceof NavigableSet)
            return (NavigableSet<T>)c;

        if(comparator == null)
            return new TreeSet<T>(c);
        */

        NavigableSet<T> result = new TreeSet<T>(comparator);
        result.addAll(c);

        return result;
    }


    public static Set<Resource> getAllTypedDBpediaResources(ISparqlEndpoint sparqlEndpoint, Collection<String> defaultGraphNames)
    {
        String query = "Select Distinct ?s " + createFromClause(defaultGraphNames) + "{ ?s a <" + OWL.Thing + "> . Filter(?s Like <http://dbpedia.org/resource/%>) . }";
        return getSubjectsCore(sparqlEndpoint, defaultGraphNames, query, "s");
    }

    /**
     *
     * @param category
     * @param categoryMember must be a member of the category,
     * @param maxDepth maximum path lengths to deviate from the category. May currenly be only 1 or 2.
     * @return
     */
    public static Sample<Resource> getNavigationExamples(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, Resource categoryMember, Resource category, int maxDepth)
    {
        List<String> queries = NavigationExampleFinder.getNavigationQueries(categoryMember.toString(), category.toString());
        String query = queries.get(maxDepth - 1);

        Iterable<QuerySolution> querySolutions = new QueryCollection(sparqlEndpoint, query).asJava();

        Sample<Resource> result = Sample.create();
        for(QuerySolution qs : querySolutions) {
            RDFNode rawPos = qs.get("pos");
            RDFNode rawNeg = qs.get("neg");

            if(rawPos.isResource()) {
                result.getPositives().add((Resource)rawPos);
            }

            if(rawNeg.isResource()) {
                result.getNegatives().add((Resource)rawNeg);
            }
        }

        return result;
    }


    /**
     *
     *
     * @param sparqlEndpoint
     * @param classes
     * @return
     */
    public static Map<Resource, Set<Resource>> getClassHierarchy(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, Collection<Resource> classes, boolean includeAll)
    {
        if(classes == null) {
            return getDirectSuperClasses(sparqlEndpoint, graphNames, null, includeAll);
        }
        else {
            // FIXME Not sure if this code is working
            Map<Resource, Set<Resource>> result = new HashMap<Resource, Set<Resource>>();

            if(includeAll) {
                MultiMaps.addAllKeys(result, classes);
            }

            Set<Resource> tmp = MyCollectionUtils.toSet(classes);

            while(!tmp.isEmpty()) {
                Map<Resource, Set<Resource>> part = getDirectSuperClasses(sparqlEndpoint, graphNames, tmp, false);

                MultiMaps.putAll(result, part);

                tmp = new HashSet<Resource>(MultiMaps.values(part));
                tmp.removeAll(result.keySet());
            }

            return result;
        }
    }

    public static Set<Resource> getDBpediaSubjects(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, Integer limit)
    {
        String limitPart = limit == null ? "" : " LIMIT " + limit;
        return getSubjectsCore(sparqlEndpoint, graphNames, "Select Distinct(?s) " + createFromClause(graphNames) + "{ ?s a ?o . Filter(?s Like <http://dbpedia.org/resource/%>). }" + limitPart, "s");
    }

    public static Set<Resource> getSubjects(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames)
    {
        return getSubjectsCore(sparqlEndpoint, graphNames, "Select Distinct(?s) " + createFromClause(graphNames) + "{ ?s ?p ?o . }", "s");
    }

    public static Set<Resource> getSubjectsCore(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, String query, String variableName)
    {
        //System.out.println("Query: " + query);

        QueryCollection qc = new QueryCollection(sparqlEndpoint, query);

        Set<Resource> result = new HashSet<Resource>();
        for(QuerySolution qs : qc.asJava()) {
            RDFNode s = qs.get(variableName);
            if(s instanceof Resource) {
                Resource ss = (Resource)s;

                result.add(ss);
            }
        }

        return result;
    }

    public static <T> Map<T, Set<T>>transitiveClosure(Map<T, Set<T>> source, Comparator<? super T> comparator)
    {
        Map<T, Set<T>> result = MultiMaps.copy(source);
        makeTransitive(result, comparator);

        return result;
    }

    /**
     * Computes the transitive closure of a multimap using Warshall's algorithm.
     *
     * @param source
     * @param <T>
     * @return
     */
    public static <T> void makeTransitive(Map<T, Set<T>> source, Comparator<? super T> comparator)
    {
        //NavigableSet<T> nodes = new TreeSet<T>(comparator);
        //nodes.addAll(source.keySet());
        List<T> nodes = new ArrayList<T>(source.keySet());

        for(T b : nodes) {
            for(T a : nodes) {
                if(MultiMaps.containsEntry(source, a, b)) {
                    for(T c : nodes) {
                        if(MultiMaps.containsEntry(source, b, c)) {
                            MultiMaps.put(source, a, c);
                        }
                    }
                }
            }
        }
    }

    public static String createFromClause(Collection<String> graphNames)
    {
        String result = "";
        for(String graphName : graphNames) {
            result += "From <" + graphName + "> ";
        }

        return result;
    }


    public static TreeMultimap<Integer, Resource> getCategoriesWithTypedMembersAndWithoutNumbers(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames)
    {
        String query =
            "Select ?cat, ?c " + createFromClause(graphNames) + "{\n" +
            "    {\n" +
            //"        Select ?o, Count(*) As ?c " + createFromClause(graphNames) + "{\n" +
            "        Select ?cat, Count(*) As ?c {\n" +
            "            ?s <" + Skos.subject + "> ?cat .\n" +
            "            Filter(Regex(?cat, \"^http://dbpedia.org/resource/Category:\")) .\n" +
            "            ?s a owl:Thing .\n" + // works because each dbpedia resource has type owl:Thing
            "        }\n" +
            "    } .\n" +
            "    Filter(?c >= 100 && ?c <= 1000) .\n" +
            "} "; //Order By Desc(?c) Asc(?cat)\n";

        Iterable<QuerySolution> querySolutions = new QueryCollection(sparqlEndpoint, query).asJava();


        Pattern pattern = Pattern.compile("\\d+");

        TreeMultimap<Integer, Resource> result = TreeMultimap.create(new NaturalComparator<Integer>(), new ResourceComparator());
        for(QuerySolution item : querySolutions) {
            RDFNode tmp = item.get("?cat");
            if(tmp instanceof Resource) {
                Resource resource = (Resource)tmp;
                String str = resource.toString();

                if(pattern.matcher(str).find())
                    continue;

                Integer count = Integer.parseInt(item.get("?c").asNode().getLiteralLexicalForm());

                result.put(count, resource);
            }
        }

        return result;
    }


    // Better not use this query!
    /*
    public static TreeMultimap<Integer, Resource> getCategoriesWithTypedMembersAndWithoutNumbers(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames)
    {
        String query =
                "Select ?cat, ?c " + createFromClause(graphNames) + "{\n" +
                "    {\n" +
                //"        Select ?o, Count(*) As ?c " + createFromClause(graphNames) + "{\n" +
                "        Select ?cat, Count(*) As ?c {\n" +
                "            ?s a owl:Thing .\n" + // works because each dbpedia resource has type owl:Thing
                "            ?s <" + Skos.subject + "> ?cat .\n" +
                "            Filter(Regex(?cat, \"^http://dbpedia.org/resource/Category:\")) .\n" +
                "            Optional {\n" +
                "                ?s ?tmp cat .\n" +
                "                Filter(Regex(?cat, \"[0-9]+\")) .\n" +
                "            }\n" +
                "            Filter(!Bound(?tmp)) .\n" +
                "        }\n" +
                "    } .\n" +
                "} "; //Order By Desc(?c) Asc(?cat)\n";
        System.out.println("Query: " + query);

        return extractCategoryAndCount((new QueryCollection(sparqlEndpoint, query)).asJava(), "cat", "c");
    }*/


    public static Set<Resource> getTypedDBpediaCategoryMembers(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, Resource category)
    {
        String query = "Select ?s " + createFromClause(graphNames) + "{ ?s <" + Skos.subject + "> <" + category + "> . ?s a owl:Thing . } ";
        Iterable<QuerySolution> qc = new QueryCollection(sparqlEndpoint, query).asJava();

        //System.out.println(query);

        return extractSubjects(qc, "s");
    }

    public static Set<Resource> getDBpediaCategoryMembers(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, Resource category)
    {
        String query = "Select Distinct ?s " + createFromClause(graphNames) + "{ ?s <" + Skos.subject + "> <" + category + "> . } ";
        Iterable<QuerySolution> qc = new QueryCollection(sparqlEndpoint, query).asJava();


        return extractSubjects(qc, "s");
    }


    public static Set<Resource> extractSubjects(Iterable<QuerySolution> querySolutions, String subjectVar)
    {
        Set<Resource> result = new HashSet<Resource>();

        for(QuerySolution qs : querySolutions) {

            RDFNode s = qs.get("s");

            if(s instanceof Resource) {
                Resource ss = (Resource)s;

                result.add(ss);
            }
        }
        return result;
    }


    public static Set<Resource> getSubjectsByPredicateObject(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, Property property, RDFNode object)
    {
        if(!(object instanceof Resource)) {
            // FIXME Implement for literals
            throw new RuntimeException("Not implemented for literals yet.");
        }

        Set<Resource> result = new HashSet<Resource>();

        String query = "Select Distinct ?s " + createFromClause(graphNames) + "{ ?s <" + property + "> <" + object + "> . } ";
        QueryCollection qc = new QueryCollection(sparqlEndpoint, query);

        for(QuerySolution qs : qc.asJava()) {

            RDFNode s = qs.get("s");

            if(s instanceof Resource) {
                Resource ss = (Resource)s;

                result.add(ss);
            }
        }
        return result;
    }

    /**
     *
     *
     * @param sparqlEndpoint
     * @param property
     * @param resources Constrains the subjects to the given set of resources. Unconstrained if null.
     * @param includeAll
     * @return
     */
    static Map<Resource, Set<Resource>> getResourceMappingBySubject(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames, Property property, Collection<Resource> resources, boolean includeAll)
    {
        Map<Resource, Set<Resource>> result = new HashMap<Resource, Set<Resource>>();

        if(includeAll && resources != null) {
            MultiMaps.addAllKeys(result, resources);
        }

        if(resources != null && resources.isEmpty()) {
            return result;
        }

        String filter = resources == null
            ? ""
            : "Filter(?s In (<" + Joiner.on(">,<").join(resources) + ">)) .";

        String query = "Select ?s ?o " + createFromClause(graphNames) + "{ ?s <" + property + "> ?o . " + filter + " } ";

        QueryCollection qc = new QueryCollection(sparqlEndpoint, query);
        for(QuerySolution qs : qc.asJava()) {

            RDFNode s = qs.get("s");
            RDFNode o = qs.get("o");

            if(s instanceof Resource && o instanceof Resource) {
                Resource ss = (Resource)s;
                Resource oo = (Resource)o;

                MultiMaps.put(result, ss, oo);

                if(includeAll)
                    MultiMaps.addKey(result, oo);
            }
        }

        return result;
    }




    public static TreeMultimap<Integer, Resource> getCategoriesContainingDBpediaInstances(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames) {
        String query =
                "Select ?cat, ?c " + createFromClause(graphNames) + "{\n" +
                "    {\n" +
                //"        Select ?o, Count(*) As ?c " + createFromClause(graphNames) + "{\n" +
                "        Select ?cat, Count(*) As ?c {\n" +
                "            ?s <" + Skos.subject + "> ?cat .\n" +
                "            Filter(Regex(?cat, \"^http://dbpedia.org/resource/Category:\")) .\n" +
                "            ?s a owl:Thing .\n" + // works because each dbpedia resource has type owl:Thing
                "        }\n" +
                "    } .\n" +
                "    Filter(?c = 100) .\n" +
                "} "; //Order By Desc(?c) Asc(?cat)\n";
        System.out.println("Query: " + query);

        return extractCategoryAndCount((new QueryCollection(sparqlEndpoint, query)).asJava(), "cat", "c");
    }

    public static TreeMultimap<Integer, Resource> getCategories(ISparqlEndpoint sparqlEndpoint, Collection<String> graphNames) {
        String query =
                "Select ?cat, ?c " + createFromClause(graphNames) + "{\n" +
                "    {\n" +
                //"        Select ?o, Count(*) As ?c " + createFromClause(graphNames) + "{\n" +
                "        Select ?cat, Count(*) As ?c {\n" +
                "            ?s <" + Skos.subject + "> ?cat .\n" +
                "            Filter(Regex(?cat, \"^http://dbpedia.org/resource/Category:\")) .\n" +
                "        }\n" +
                "    } .\n" +
                "    Filter(?c = 100) .\n" +
                "}\n";

        System.out.println("Query: " + query);

        return extractCategoryAndCount((new QueryCollection(sparqlEndpoint, query)).asJava(), "cat", "c");
    }


    public static TreeMultimap<Integer, Resource> extractCategoryAndCount(Iterable<QuerySolution> querySolutions, String categoryVar, String countVar)
    {
        TreeMultimap<Integer, Resource> result = TreeMultimap.create(new NaturalComparator<Integer>(), new ResourceComparator());

        //Integer x = col.iterator();
        //List<Resource> result = new ArrayList<Resource>();

        //ResultSet rs = sparqlEndpoint.executeSelect(query);
        //while(rs.hasNext()) {
        //QueryCollection qc = new QueryCollection(sparqlEndpoint, query);

        for(QuerySolution item : querySolutions) {
            RDFNode tmp = item.get(categoryVar);
            if(tmp instanceof Resource) {
                Resource resource = (Resource)tmp;
                Integer count = Integer.parseInt(item.get(countVar).asNode().getLiteralLexicalForm());

                // FIXME Make this limit a parameter
                //if(count != 100)
                //    continue;

                result.put(count, resource);
            }
        }

        return result;
    }

    public static Set<Resource> getKeywordSearchResult(ISparqlEndpoint sparqlEndpoint, Set<String> defaultGraphNames, String keywords)
    {
        List<String> queries = NavigationExampleFinder.getSearchQueries(keywords);

        Set<Resource> result = new HashSet<Resource>();
        for(String query : queries) {
            Iterable<QuerySolution> col = new QueryCollection(sparqlEndpoint, query).asJava();

            for(QuerySolution qs : col) {
                RDFNode rawS = qs.get("?s");

                if(rawS.isURIResource()) {
                    result.add((Resource)rawS);
                }
            }
        }

        return result;
    }    
}
